/* eslint-disable brace-style */

import {Injectable} from '@angular/core';
import {Router} from '@angular/router';
import {OAuthErrorEvent} from 'angular-oauth2-oidc';
import {BehaviorSubject, combineLatest, Observable} from 'rxjs';
import {filter, map} from 'rxjs/operators';
import {MyOAuthService} from "./my-oauth-service";
import {NgxPermissionsService, NgxRolesService} from "ngx-permissions";
// import {jsonwebtoken as jwt} from 'jsonwebtoken';
//import {jwtDecode} from 'jwt-decode';

@Injectable({ providedIn: 'root' })
export class AuthService {

  private isAuthenticatedSubject$ = new BehaviorSubject<boolean>(false);
  public isAuthenticated$ = this.isAuthenticatedSubject$.asObservable();

  private isDoneLoadingSubject$ = new BehaviorSubject<boolean>(false);
  public isDoneLoading$ = this.isDoneLoadingSubject$.asObservable();

  /**
   * Publishes `true` if and only if (a) all the asynchronous initial
   * login calls have completed or errorred, and (b) the user ended up
   * being authenticated.
   *
   * In essence, it combines:
   *
   * - the latest known state of whether the user is authorized
   * - whether the ajax calls for initial log in have all been done
   *
   * 当且仅当(a)所有异步初始登录调用已经完成或错误，并且(b)用户最终被认证时，发布' true '。
   * 本质上，它结合了:
   * —用户是否被授权的最新已知状态
   * —初始登录的ajax调用是否已经全部完成
   *
   */
  public canActivateProtectedRoutes$: Observable<boolean> = combineLatest([
    this.isAuthenticated$,
    this.isDoneLoading$
  ]).pipe(map(values => values.every(b => b)));

  // 导航到登录页
  private navigateToLoginPage() {
    // TODO: Remember current URL
    this.router.navigateByUrl('/should-login');
  }

  // 构造函数
  constructor(
    private oauthService: MyOAuthService,
    private router: Router,
    private permissionsService: NgxPermissionsService,
    private rolesService: NgxRolesService,
  ) {
    // Useful for debugging: 显示调试信息
    this.oauthService.events.subscribe(event => {
      if (event instanceof OAuthErrorEvent) {
        console.error('来自 auth.service.ts OAuthErrorEvent Object:', event);
      } else {
        console.warn('来自 auth.service.ts,OAuthEvent Object:', event);
      }
    });

    // THe following cross-tab communication of fresh access tokens works usually in practice,
    // but if you need more robust handling the community has come up with ways to extend logic
    // in the library which may give you better mileage.
    // 跨标签的通信的刷新访问令牌工作通常在实践中，但如果您需要更可靠的处理，社区已经提出了一些方法来扩展逻辑，这可能为您提供更好的里程。
    // 以下新访问令牌的跨选项卡通信通常在实践中有效，但如果您需要更健壮的处理，社区已经提出了扩展库中的逻辑的方法，这可能会给您带来更好的效果。
    //
    // See: https://github.com/jeroenheijmans/sample-angular-oauth2-oidc-with-auth-guards/issues/2
    //
    // Until then we'll stick to this:
    // 在那之前，我们会坚持这样做: token_refresh_error
    // window.addEventListener('storage', (event) => {
    //   // The `key` is `null` if the event was caused by `.clear()`
    //   if (event.key !== 'access_token' && event.key !== null) {
    //     return;
    //   }
    //
    //   console.warn('Noticed=== changes to access_token (most likely from another tab), updating isAuthenticated');
    //   this.isAuthenticatedSubject$.next(this.oauthService.hasValidAccessToken());
    //
    //   if (!this.oauthService.hasValidAccessToken()) {
    //     this.navigateToLoginPage();
    //   }
    // });
    //建议改进的
    window.addEventListener('storage', (event) => {
      //console.log('====event.key: ', event.key);
      if ((event.key === 'auth_data_updated' && event.newValue !== null) || event.key === null) {
        console.log('授权数据已更新 Auth data has been updated in localStorage =====');
        this.isAuthenticatedSubject$.next(this.oauthService.hasValidAccessToken());

        if (!this.oauthService.hasValidAccessToken()) {
          this.navigateToLoginPage();
        }
      }
      else if(event.key !== 'auth_data_updated' && event.key !== null){return;}

    });

    // 监听事件
    this.oauthService.events
      .subscribe(event => {

        // 重新引导文件
        if (event.type === 'discovery_document_loaded') {
          if (this.oauthService.hasValidAccessToken() && this.oauthService.hasValidIdToken()) {
            // @ts-ignore
            if (event.hasOwnProperty('info') && event.info !== null){
              console.log('只是刷新页面, 加载权限服务 discovery_document_loaded,');
              this.loadPermissionsServices();
            }
          }

          if (!this.oauthService.hasValidAccessToken()) {
            console.log('只是刷新页面, discovery_document_loaded,');
            this.oauthService.silentRefresh();
          }
         }

        // 令牌到期
        if (event.type === 'token_expires') {
          // token_refreshed,
          // token_received
          /**
           * export declare type EventType = 'discovery_document_loaded' | 'jwks_load_error' | 'invalid_nonce_in_state' |
           *                                 'discovery_document_load_error' | 'discovery_document_validation_error' |
           *                                 'user_profile_loaded' | 'user_profile_load_error' | 'token_received' |
           *                                 'token_error' | 'code_error' | 'token_refreshed' | 'token_refresh_error' |
           *                                 'silent_refresh_error' | 'silently_refreshed' | 'silent_refresh_timeout' |
           *                                 'token_validation_error' | 'token_expires' | 'session_changed' | 'session_error' |
           *                                 'session_terminated' | 'session_unchanged' | 'logout' | 'popup_closed' |
           *                                 'popup_blocked' | 'token_revoke_error';

           * */
          console.log('令牌到期 token_expires');
          // this.oauthService.silentRefresh().then(() => {
          //   console.log('静默刷新成功 silently_refreshed');
          // }).catch(error => {
          //   console.log('静默刷新失败 silent_refresh_error', error);
          //   this.navigateToLoginPage();
          // });

          //if (event.type === 'silent_refresh_timeout')
          // {
          //
          this.oauthService.refreshToken().then(()=>{
            console.log('刷新成功 token_refreshed');
            if (this.oauthService.hasValidIdToken()){
              console.log("准备刷新，引导权限模块2，，，");
              this.loadPermissionsServices();
            }
          }).catch(error => {
            console.log('刷新令牌 token_refresh_error', error);
            //this.oauthService.initLoginFlow();
            this.navigateToLoginPage();
          });
          // }
        }

        //this.oauthService.tokenValidationHandler = new JwksValidationHandler()

        if (event.type === 'silent_refresh_timeout') {
          console.log('静默刷新超时 silent_refresh_timeout');
          //this.oauthService.setupAutomaticSilentRefresh()
          // this.oauthService.refreshToken().then(()=>{
          //   console.log('刷新成功 silently_refreshed');
          // }).catch(error => {
          //   console.log('刷新失败 silent_refresh_error', error);
          //   this.navigateToLoginPage();
          // });
        }

        if(event.type === 'token_refresh_error'){
          console.log('刷新令牌 token_refresh_error');
          this.navigateToLoginPage();
        }

        if (event.type === 'token_received') {
          //console.log('令牌接收 token_received');
          // 刷新令牌
          // this.oauthService.refreshToken().then(()=>{
          //   console.log('刷新令牌 token_refreshed');
          // }).catch(error => {
          //   console.log('刷新令牌 token_refresh_error', error);
          //   this.navigateToLoginPage();
          // });
        }

        // 原来的更新身份令牌
        this.isAuthenticatedSubject$.next(this.oauthService.hasValidAccessToken());
      });
    this.isAuthenticatedSubject$.next(this.oauthService.hasValidAccessToken());

    //加载用户配置文件:
    this.oauthService.events
      .pipe(filter(e => ['token_received'].includes(e.type)))
      .subscribe(e => {
        console.log('令牌收到 token_received 开始加载用户配置文件userinfo:');
        this.oauthService.loadUserProfile().then((userInfo)=>{
          console.log('用户信息 userinfo:', userInfo);
        });
        // 测试用户信息
        // if(this.oauthService.hasValidIdToken()){
        //   const idToken = this.oauthService.getIdToken();
        //   const decodedToken = jwtDecode(idToken);
        //   const scopes = this.oauthService.getGrantedScopes();
        //   const claims = this.oauthService.getIdentityClaims();
        //   console.log('身份令牌 Identity Token===:', decodedToken);
        //   console.log('范围 Granted Scopes===:', scopes);
        //   console.log('组件 Identity Claims===:', claims);
        // }
      });


    // 赋值权限zmhhxl
    this.oauthService.events
      .pipe(filter(e => ['token_received', 'user_profile_loaded'].includes(e.type)))
      .subscribe(e => {
        if(this.oauthService.hasValidIdToken()){
            console.log("引导权限模块，，，")
          this.loadPermissionsServices();
        }
      });

    // 静默刷新,现在不许出现
    this.oauthService.events
      .pipe(filter(e => ['silently_refreshed'].includes(e.type)))
      .subscribe(e => {
        console.log('静默刷新成功2 silently_refreshed ' + this.oauthService.hasValidIdToken());
        if (this.oauthService.hasValidAccessToken() && (this.oauthService.getIdentityClaims()==null)){
          console.log("找回令牌扩展信息,执行refreshToken()")
          this.oauthService.refreshToken();
          }});

    // 处理会话终止和错误,silently_refreshed silent_refresh_timeout
    this.oauthService.events
      .pipe(filter(e => ['session_terminated', 'session_error'].includes(e.type)))
      .subscribe(e => this.navigateToLoginPage());

    //this.oauthService.initLoginFlow();
    this.oauthService.setupAutomaticSilentRefresh();   //原有的
    //this.silentRefreshPostMessageEventListener();
  }  //end constructor 构造函数结束

  /** silentRefreshPostMessageEventListener
   * 执行初始登录序列。
   * 该函数首先检查URL中的哈希片段，如果有，则将其解析并打印。
   * 接着配置OAuth服务，并尝试通过三种方式完成登录：通过哈希登录、静默登录和用户交互登录。
   * 如果登录成功，会根据OAuth服务中的state值进行页面导航。
   *
   * @returns {Promise<void>} 一个Promise，表示异步登录序列完成。
   */
  public runInitialLoginSequence(): Promise<void> {
    // 检查URL中的哈希片段并进行解析打印
    if (location.hash) {
      console.log('Encountered hash fragment, plotting as table...');
      console.table(location.hash.substr(1).split('&').map(kvp => kvp.split('=')));
    }

    //this.oauthService.configure(authConfig);

    // 0. LOAD CONFIG:
    // First we have to check to see how the IdServer is
    // currently configured:
    // 加载发现文档，配置身份验证服务器  'http://127.0.0.1:9000/.well-known/openid-configuration'
    return this.oauthService.loadDiscoveryDocument()

      // For demo purposes, we pretend the previous call was very slow
      // // 模拟延迟，为了演示目的
      .then(() => new Promise<void>(resolve => setTimeout(() => resolve(), 3000)))

      // 1. HASH LOGIN:
      // Try to log in via hash fragment after redirect back
      // from IdServer from initImplicitFlow:
      // 委托给tryLoginImplicitFlow是避免竞争
      // 尝试通过哈希登录
      .then(() => this.oauthService.tryLogin(
        {
        onTokenReceived: (e) => {
          console.log('onTokenReceived', e);
          // this.oauthService.initImplicitFlow();
        },
          disableNonceCheck: false,
          disableOAuth2StateCheck: false,
          //customRedirectUri: '',
        }
      ))

      //可能需要检查您之前是否经过身份验证
      .then(() => {
        if (this.oauthService.hasValidAccessToken()) {
          // const token = this.oauthService.getAccessToken();
          // const scopes = this.oauthService.getGrantedScopes();
          // const claims = this.oauthService.getIdentityClaims();
          // console.log('范围 Granted Scopes===:', scopes);
          // console.log('组件 Identity Claims===:', claims);
          // 登录成功，可以访问用户信息
          return Promise.resolve();
        }

        // 登录成功，可以访问用户信息
        if (this.oauthService.getIdentityClaims()==null) {
          // this.oauthService.refreshToken().then(()=>Promise.resolve()).catch(error => {
          //   console.log('刷新令牌 token_refresh_error', error);
          //   this.navigateToLoginPage();
          // });
          // 登录成功，可以访问用户信息
          return Promise.resolve();
        }

        if (this.oauthService.getAccessToken()==null) {
          // 登录成功，可以访问用户信息
          return Promise.resolve();
        }

        // 2. SILENT LOGIN:
        // 尝试静默登录
        // Try to log in via a refresh because then we can prevent
        // needing to redirect the user:
        return this.oauthService.silentRefresh()
          .then(() => Promise.resolve())
          .catch(result => {
            // Subset of situations from https://openid.net/specs/openid-connect-core-1_0.html#AuthError
            // Only the ones where it's reasonably sure that sending the
            // user to the IdServer will help.
            // 处理需要用户交互的登录错误情况
            const errorResponsesRequiringUserInteraction = [
              'interaction_required',
              'login_required',
              'account_selection_required',
              'consent_required',
            ];

            if (result
              && result.reason
              && errorResponsesRequiringUserInteraction.indexOf(result.reason.error) >= 0) {

              // 3. ASK FOR LOGIN:
              // At this point we know for sure that we have to ask the
              // user to log in, so we redirect them to the IdServer to
              // enter credentials.
              //
              // Enable this to ALWAYS force a user to login.
              // this.login();
              //
              // Instead, we'll now do this:
              // 需要用户交互，打印警告并等待用户手动登录
              console.warn('zmhhxl User interaction is needed to log in, we will wait for the user to manually log in.');
              return Promise.resolve();
            }

            // We can't handle the truth, just pass on the problem to the
            // next handler.
            // 无法处理的错误，传递给下一个处理器
            return Promise.reject(result);
          }); // end then 静默登录
      })

      // 登录流程完成后的处理
      .then(() => {
        this.isDoneLoadingSubject$.next(true);

        // Check for the strings 'undefined' and 'null' just to be sure. Our current
        // login(...) should never have this, but in case someone ever calls
        // initImplicitFlow(undefined | null) this could happen.
        // 检查字符串'undefined'和'null'以确保。我们当前的登录(…)不应该有这个，
        // 但万一有人调用inittimplicitflow (undefined | null)，这可能会发生。
        // 根据state值进行页面导航
        if (this.oauthService.state && this.oauthService.state !== 'undefined' && this.oauthService.state !== 'null') {
          let stateUrl = this.oauthService.state;
          if (stateUrl.startsWith('/') === false) {
            stateUrl = decodeURIComponent(stateUrl);
          }
          console.log(`There was state of ${this.oauthService.state}, so we are sending you to: ${stateUrl}`);
          this.router.navigateByUrl(stateUrl);
        }
      })
      // 捕获任何错误，并结束加载状态
      .catch(() => this.isDoneLoadingSubject$.next(true));
  }

  //这些方法目前只是代理内部服务，但是
  //在实际场景中，它们也可能会改变
  public login(targetUrl?: string) {
    // Note: before version 9.1.0 of the library you needed to
    // call encodeURIComponent on the argument to the method.
    // 注意:在9.1.0版本之前的库需要在方法的参数上调用encodeURIComponent
    // login/oauth2/code/messaging-client-angular-oidc
    console.log('====登录===='+targetUrl+'+++'+this.router.url)
    // 根据您的配置，启动隐式流或代码流。
    //this.oauthService.initLoginFlow("http://127.0.0.1:9000/login");
    //启动授权代码流，并将用户重定向到授权服务器登录url。
    this.oauthService.initCodeFlow(targetUrl || this.router.url)
    //this.oauthService.setupAutomaticSilentRefresh();
  }

  public logout() {
    // @ts-ignore                                                                                                                                            // window.location.origin + '/logout-endpoint'
    this.oauthService.logOut({id_token_hint: this.oauthService.getIdToken(), sub: this.oauthService.getIdentityClaims().sub, postLogoutRedirectUri: this.oauthService.logoutUrl});
    //this.oauthService.revokeTokenAndLogout();
  }

  public refresh() { this.oauthService.silentRefresh(); }

  public refreshTokenR(){this.oauthService.refreshToken();}

  public hasValidToken() { return this.oauthService.hasValidAccessToken(); }

  // These normally won't be exposed from a service like this, but
  // for debugging it makes sense.
  public get accessToken() { return this.oauthService.getAccessToken(); }
  public get refreshToken() { return this.oauthService.getRefreshToken(); }
  public get identityClaims() { return this.oauthService.getIdentityClaims(); }
  public get idToken() { return this.oauthService.getIdToken(); }
  public get logoutUrl() { return this.oauthService.logoutUrl; }
  //public get givenName() { return this.oauthService.getIdentityClaims().sub; }

  private  loadPermissionsServices() {
    const claims = this.oauthService.getIdentityClaims();
    // const user = this.oauthService.;
    if (!claims) {
      return ;
    }

    // @ts-ignore
    const roles = claims.authorities; //'].split(',');
    // @ts-ignore
    console.log("======claims "+roles);
    if (roles) {
      //return [];
       this.permissionsService.loadPermissions(roles);
       // @ts-ignore
      this.rolesService.addRoles({name:claims.sub, roles:roles});
      //console.log("======roles "+this.rolesService.getRoles())
      console.log("======permissions "+this.permissionsService.getPermissions())
    }
    return ;
  }
}
